package com.cheney.demo.algorithm.backtrack;

import java.util.*;

public class EightQueens {
    // 方法一：暴力穷举
    public List<int[]> eightQueens1(){
        // 定义保存结果的List
        ArrayList<int[]> result = new ArrayList<>();

        // 用一个int[8]数组保存一组解
        int[] solution = new int[8];

        // 遍历八皇后每种可以摆放的场景，判断是否符合题目限制
        for (solution[0] = 0; solution[0] < 8; solution[0] ++){
            for (solution[1] = 0; solution[1] < 8; solution[1] ++){
                for (solution[2] = 0; solution[2] < 8; solution[2] ++){
                    for (solution[3] = 0; solution[3] < 8; solution[3] ++){
                        for (solution[4] = 0; solution[4] < 8; solution[4] ++){
                            for (solution[5] = 0; solution[5] < 8; solution[5] ++){
                                for (solution[6] = 0; solution[6] < 8; solution[6] ++){
                                    for (solution[7] = 0; solution[7] < 8; solution[7] ++){
                                        if (check(solution))
                                            result.add(Arrays.copyOf(solution, 8));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return result;
    }

    // 定义一个判定当前摆放方式是否有效的方法
    private boolean check(int[] a){
        // 任意两个皇后进行比较
        for (int i = 0; i < 7; i++){
            for (int j = i + 1; j < 8; j++){
                // 不能在同一列，并且行列索引差不能相等
                if (a[i] == a[j] || Math.abs(a[i] - a[j]) == j - i)
                    return false;
            }
        }
        return true;
    }

    // 方法二：回溯
    HashSet<Integer> cols = new HashSet<>();
    HashSet<Integer> diags1 = new HashSet<>();
    HashSet<Integer> diags2 = new HashSet<>();

    public List<int[]> eightQueens(){
        // 定义保存结果的List
        ArrayList<int[]> result = new ArrayList<>();

        // 用一个int[8]数组保存一组解
        int[] solution = new int[8];

        // 先对solution做初始填充，表示皇后还没有填充
        Arrays.fill(solution, -1);

        // 定义回溯方法，递归调用
        backtrack(result, solution, 0);

        return result;
    }

    // 实现回溯方法
    private void backtrack(ArrayList<int[]> result, int[] solution, int row){
        // 首先处理递归调用结束场景
        if (row >= 8){
            // 已经直接得到了所有行的填充结果，构建了一组解
            result.add(Arrays.copyOf(solution, 8));
        } else {
            // 对于当前行，考察可能的皇后位置，遍历每一列
            for (int column = 0; column < 8; column ++){
                // 1.如果已经和之前的皇后冲突，直接跳过，寻找下一位置
                // 1.1 判断同一列
                if (cols.contains(column))
                    continue;
                // 1.2 判断两条斜线
                int diag1 = row - column;
                int diag2 = row + column;
                if (diags1.contains(diag1))
                    continue;
                if (diags2.contains(diag2))
                    continue;

                // 2. 如果不冲突，当前位置就放置皇后
                solution[row] = column;
                cols.add(column);
                diags1.add(diag1);
                diags2.add(diag2);

                // 3. 递归调用，深度搜索下一行
                backtrack(result, solution, row + 1);

                // 4. 回溯，将状态回滚，继续遍历当前行皇后可能的位置
                solution[row] = -1;
                cols.remove(column);
                diags1.remove(diag1);
                diags2.remove(diag2);
            }
        }
    }

    public static void main(String[] args) {
        EightQueens eightQueens = new EightQueens();

        List<int[]> result = eightQueens.eightQueens();

        System.out.println("八皇后问题一共有" + result.size() + "种不同的摆法");

        for (int[] solution: result){
            printQueens(solution);

            System.out.println("=================");
        }
    }

    // 打印解的方法
    public static void printQueens(int[] queens){
        int n = queens.length;
        // 打印每一行
        for (int i = 0; i < n; i++){
            String[] line = new String[n];
            Arrays.fill(line, "□");
            line[queens[i]] = "Q";
            for (String str: line)
                System.out.print(str + "\t");

            System.out.println();
        }
    }
}
